package com.feinno.urcs.group.servlet;


/**
 * @author Li.Hongbo <lihongbo@feinno.com>
 */

import com.feinno.msrp.MsrpSession;
import com.feinno.sip.address.AddressImpl;
import com.feinno.sip.address.SipUri;
import com.feinno.sip.header.*;
import com.feinno.sip.header.ims.PCalledPartyID;
import com.feinno.sip.message.SipRequest;
import com.feinno.sip.message.SipResponse;
import com.feinno.sip.transaction.TransactionClient;
import com.feinno.sip.transaction.TransactionClientListener;
import com.feinno.urcs.content.*;
import com.feinno.urcs.dns.EnumService;
import com.feinno.urcs.group.*;
import com.feinno.urcs.group.api.*;
import com.feinno.urcs.group.entity.*;
import com.feinno.urcs.group.siptask.GroupByeTask;
import com.feinno.urcs.group.siptask.GroupInviteTask;
import com.feinno.urcs.group.siptask.GroupSessionMessageTask;
import com.feinno.urcs.group.siptask.task.*;
import com.feinno.urcs.group.siputils.GroupHeaderUtils;
import com.feinno.urcs.group.siputils.URIUtils;
import com.feinno.urcs.mobile.UserLocatorService;
import com.feinno.urcs.mobile.entity.UserType;
import com.feinno.urcs.nai.IMPU;
import com.feinno.urcs.profile.UserSessionService;
import com.feinno.urcs.profile.entity.ClientType;
import com.feinno.urcs.sip.SipConnector;
import com.feinno.urcs.sip.UrcsSipFactory;
import com.feinno.urcs.sipservlet.*;
import com.feinno.urcs.sipservlet.message.SipMessageBuilder;
import com.feinno.urcs.sipservlet.msrp.MsrpSessionFactory;
import com.feinno.urcs.sipservlet.msrp.MsrpUtils;
import com.feinno.urcs.types.MobileNo;
import com.feinno.urcs.utils.GzipUtils;
import com.feinno.util.Combo2;
import com.feinno.util.StringUtils;
import org.helium.framework.annotations.FieldSetter;
import org.helium.framework.annotations.ServiceSetter;
import org.helium.framework.annotations.TaskEvent;
import org.helium.framework.task.DedicatedTaskContext;
import org.helium.framework.task.TaskProducer;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import javax.sdp.SdpException;
import javax.sdp.SessionDescription;
import javax.sip.InvalidArgumentException;
import javax.sip.address.Address;
import javax.sip.address.SipURI;
import javax.sip.address.TelURI;
import javax.sip.address.URI;
import javax.sip.header.Header;
import javax.sip.message.Request;
import java.nio.charset.Charset;
import java.text.ParseException;
import java.util.*;

public class GroupServlet extends DialogSipServlet {
	/**
	 * 日志对象
	 */
	private static final Logger LOGGER = LoggerFactory.getLogger(GroupServlet.class);
	private static final String REASON_CALL_COMPLETED = "Call completed";
	public static final String TASK_CONTEXT_NAME = "T";
	private GroupByeTask byeTask = new GroupByeTask(this);
	private GroupSessionMessageTask sessionMessageTask = new GroupSessionMessageTask(this);
	private GroupInviteTask inviteTask = new GroupInviteTask(this);

	/**
	 * 群组服务
	 */
	@FieldSetter("${AS_ADDRESS}")
	private String asAddress;
	@FieldSetter("${CSCF_ADDRESS}")
	private String cscfAddress;
	@FieldSetter("${ISBC_ADDRESS}")
	private String isbcAddress;
	@FieldSetter(loader = SipConnectorLoader.class, value = "${SIP_STACK_AS}")
	private SipConnector connector;
	@ServiceSetter
	private GroupService groupService;
	@ServiceSetter
	private UserLocatorService userLocatorService;
	@ServiceSetter
	private GroupSubscriptionManager subscriptionManager;
	@ServiceSetter
	private MsrpSessionFactory msrpFactory;
	@ServiceSetter
	private GroupChatService chatService;
	@ServiceSetter
	private GroupOfflineMessageService offlineMessageService;
	@ServiceSetter
	private UserSessionService userSessionService;
	@ServiceSetter
	private EnumService enumService;
	@TaskEvent(GroupTaskEvents.GROUP_BYE)
	private TaskProducer<GroupByeArgs> outterByeTask;
	@ServiceSetter
	private GroupMessageLogService groupMessageLogService;


	/**
	 * Servelt要处理的信令情况
	 * 1. 创建群
	 * 2. 被邀请方接受邀请
	 * 3. 激活群
	 * 如何区分出这三种情况
	 *
	 * @param servletRequest
	 */
	@Override
	public void doInvite(SipServletRequest servletRequest) {
		try {
			inviteHandler(servletRequest);
		} catch (GroupException ex) {
			try {
				GroupError groupnameExceedLimit = ex.getError();
				Warning warning = new Warning();
				warning.setAgent(groupnameExceedLimit.getAgent());
				warning.setCode(groupnameExceedLimit.getWarningCode());
				warning.setText(groupnameExceedLimit.getText());
				SipResponse response = servletRequest.createResponse(groupnameExceedLimit.getSipCode());
				response.setHeader(warning);
				servletRequest.sendResponse(response);
			} catch (ParseException e) {
				LOGGER.info("GroupServlet.doInvite catch ParseException {}", ex);
				throw new RuntimeException(ex);
			} catch (InvalidArgumentException e) {
				LOGGER.info("GroupServlet.doInvite catch InvalidArgumentException {}", ex);
				throw new RuntimeException(ex);
			}
		} catch (Exception ex) {
			LOGGER.error("GroupServlet.doInvite failed: {}", ex);
			throw new RuntimeException(ex);
		}
	}

	@Override
	public void doReInvite(SipServletRequest servletRequest) {
		try {
			servletRequest.sendResponse(180, "Ringing");
			String toUri = GroupHeaderUtils.getRequestUriStr(servletRequest);
			MobileNo fromMobileNo = MobileNo.parseFrom(URIUtils.getMobileNo(GroupHeaderUtils.getFromUriByPAssertedIdentity(servletRequest)));
			if (fromMobileNo == null) {
				fromMobileNo = MobileNo.parseFrom(URIUtils.getMobileNo(GroupHeaderUtils.getFromUri(servletRequest)));
			}
			groupService.check(fromMobileNo, toUri);
			IMPU groupIMPU = IMPU.fromUri(servletRequest.getRequestURI());
			String groupUser = URIUtils.getMobileNo(groupIMPU.toUri());
			int groupId = Integer.parseInt(groupUser.substring(GroupConfiguration.INSTANCE.getGroupPrefix().length()));
			inviteResponse(servletRequest, fromMobileNo, groupId);
		} catch (GroupException ex) {
			LOGGER.info("GroupServlet.doInvite catch {}", ex);
			try {
				GroupError error = ex.getError();
				Warning warning = new Warning();
				warning.setAgent(error.getAgent());
				warning.setCode(error.getWarningCode());
				warning.setText(error.getText());
				SipResponse response = servletRequest.createResponse(error.getSipCode());
				response.setHeader(warning);
				servletRequest.sendResponse(response);
			} catch (ParseException e) {
				LOGGER.info("GroupServlet.doReInvite catch ParseException {}", ex);
				throw new RuntimeException(ex);
			} catch (InvalidArgumentException e) {
				LOGGER.info("GroupServlet.doReInvite catch InvalidArgumentException {}", ex);
				throw new RuntimeException(ex);
			}
		} catch (Exception ex) {
			LOGGER.error("GroupServlet.doReInvite failed: {}", ex);
			throw new RuntimeException(ex);
		}
	}

	@Override
	public void doAck(SipServletRequest sipServletRequest) {

	}

	@Override
	public void doCall(SipSession sipSession) {

	}

	@Override
	public void doBye(SipServletRequest sipServletRequest) {
		try {
			byeGroup(sipServletRequest);
		} catch (Exception ex) {
			LOGGER.error("GroupServlet.doBye failed: {}", ex);
		}
	}

	@Override
	public void doCancel(SipServletRequest sipServletRequest) {

	}

	@Override
	public void doClosed(SipSession sipSession) {


	}

	@Override
	public void onDlgTimeout(SipSession sipSession) {

	}

	@Override
	public void onSessionTimeout(SipSession sipSession) {

	}

	@Override
	public void doRefer(SipServletRequest servletRequest) {
		LOGGER.info("GroupServlet.doRefer.");
		try {
			ReferTo referToHeader = (ReferTo) servletRequest.getHeader(ReferTo.NAME);
			URI referToUri = referToHeader.getAddress().getURI();
			boolean isSip = referToUri.isSipURI();
			String method = StringUtils.EMPTY;
			try {
				if (referToHeader.getParameter("method") != null) {
					method = referToHeader.getParameter("method");
				} else if (isSip && ((SipUri) referToUri).getParameter("method") != null) {
					method = ((SipURI) referToUri).getParameter("method");
				} else if (!isSip && ((TelURI) referToUri).getParameter("method") != null) {
					method = ((TelURI) referToUri).getParameter("method");
				}
			} catch (Exception ex) {
			}
			switch (method) {
				case "INVITE":
					inviteMember(servletRequest);
					break;
				case "SUBJECT":
					modifySubject(servletRequest);
					break;
				case "MUNN":
					modifyNickName(servletRequest);
					break;
				case "SETPLC":
					changeAdmin(servletRequest);
					break;
				case "BYE":
					kickGroupMember(servletRequest);
					break;
				default:
					referMultiple(servletRequest);
			}
			return;
		} catch (ParseException e) {
			LOGGER.error("doRefer occur error :{}", e);
			servletRequest.sendResponse(400);
		} catch (Exception e) {
			LOGGER.error("doRefer occur error :{}", e);
			servletRequest.sendResponse(500);
		}
	}

	private void referMultiple(SipServletRequest servletRequest) throws Exception {
		LOGGER.info("GroupServlet.referMultiple....");
		boolean multipleRefer = false;
		ListIterator<SIPHeader> headers = servletRequest.getHeaders(Require.NAME);
		while (headers.hasNext()) {
			SIPHeader next = headers.next();
			if (next.getValue().equalsIgnoreCase("multiple-refer")) {
				multipleRefer = true;
			}
		}
		MobileNo fromMobileNo = (MobileNo) servletRequest.getSession().get(GroupSessionKeys.USER_FROM_MOBILE_NO);
		if (fromMobileNo == null) {
			fromMobileNo = MobileNo.parseFrom(URIUtils.getMobileNo(GroupHeaderUtils.getFromUri(servletRequest)));
		}
		if (multipleRefer) {
			// 此处为邀请多人
			//解析XML.  如果携带列表, 则需要解析列表,并得到列表
			byte[] content = servletRequest.getRawContent();
			ContentEncoding encodingHeader = (ContentEncoding) servletRequest.getHeader(SIPHeader.CONTENT_ENCODING);
			if (encodingHeader != null && "gzip".equalsIgnoreCase(encodingHeader.getValue().trim())) {
				if (GzipUtils.isGzip(content)) {
					content = GzipUtils.uncompress(content);
				}
			}
			ResourceListArgs resourceListArgs = new ResourceListArgs();
			resourceListArgs.parseXmlFrom(new String(content, "UTF-8"));
			boolean isInvite = true;
			List<MobileNo> members = new ArrayList<>();
			for (ResourceListArgs.Entry entry : resourceListArgs.getEntryList()) {
				String uriStr = entry.getUri().toLowerCase();
				if (uriStr.contains("?method=bye")) {
					isInvite = false;
					uriStr = uriStr.substring(0, uriStr.indexOf("?"));
					URI uri = UrcsSipFactory.getAddressFactory().createURI(uriStr);
					String mobileNo = URIUtils.getMobileNo(uri);
					members.add(MobileNo.parseFrom(mobileNo));
				} else {
					URI uri = UrcsSipFactory.getAddressFactory().createURI(entry.getUri());
					String mobileNo = URIUtils.getMobileNo(uri);
					members.add(MobileNo.parseFrom(mobileNo));
				}

			}
			MobileNo[] a = new MobileNo[members.size()];
			for (int i = 0; i < members.size(); i++) {
				a[i] = members.get(i);
			}
			SipResponse response = servletRequest.createResponse(202);
			int groupId = GroupHeaderUtils.parseGroupId(servletRequest);
			GroupIndex index = groupService.getGroupIndex(groupId);
			if (a.length <= 0) {
				servletRequest.sendResponse(400);
				return;
			}
			if (isInvite) {
				groupService.inviteGroupMembers(index, fromMobileNo, a);
			} else {
				groupService.removeGroupMembers(index, fromMobileNo, a);
			}
			response.setReasonPhrase("Accepted");
			servletRequest.sendResponse(response);
		} else {
			LOGGER.warn("miss require header");
			servletRequest.sendResponse(400);
			return;
		}
	}

	@Override
	public void doReceiveRequest(SipServletRequest sipServletRequest) {

	}

	/**
	 * 用来处理invite 以及reinvite 的应答.
	 *
	 * @param request
	 * @param fromMobileNo
	 * @param groupId
	 * @throws Exception
	 */
	private void inviteResponse(SipServletRequest request, MobileNo fromMobileNo, int groupId) throws Exception {
		// 从servletRequest创建Response: 不用设置Via,To,From,Call-ID,CSeq,Record-Route,Allow,Date
		SipURI groupUri = UrcsSipFactory.getAddressFactory().createSipURI(GroupConfiguration.INSTANCE.getGroupPrefix() + groupId, GroupConfiguration.INSTANCE.getGroupDomain());
		SipResponse response = request.createResponse(200, "OK");
		SipMessageBuilder builder = new SipMessageBuilder(response)
				.pai(UrcsSipFactory.getAddressFactory().createSipURI(GroupConfiguration.INSTANCE.getGroupPrefix() + groupId, GroupConfiguration.INSTANCE.getGroupDomain()))
				.sessionExires(3600 * 24, "uac")
				.supported("100rel,timer")
				.addHeader(new Require("timer"))
				.minSE(600)
				.recordRoute(UrcsSipFactory.getAddressFactory().createAddress(asAddress))
//				.contact(GroupConfiguration.INSTANCE.getGroupPrefix() + groupInfo.getGroupId(), GroupConfiguration.INSTANCE.getGroupDomain(), GroupConstants.G_3GPP_ICSI_REF_SESSION_KEY_VALUE, true)
				.allow("ACK,BYE,CANCEL,INFO,INVITE,MESSAGE,NOTIFY,OPTIONS,PRACK,UPDATE,REFER");
		GroupMemberDialog memberDialog = newGroupMemberDialog(fromMobileNo);
		try {
			MsrpSession msrp = msrpFactory.createSession();
			SessionDescription sdp = MsrpUtils.getSdp(request);
			String acceptType = MsrpUtils.getMsrpAcceptType(sdp);
			String wrappedType = MsrpUtils.getMsrpAcceptWrappedType(sdp);
			byte[] sdpBytes = MsrpUtils.createMsrpSdp(msrp.getSessionId(), msrp.getLocalSocketAddress(), false, acceptType, MsrpUtils.MsrpTransferMode.SENDRECV, wrappedType);
			builder.content(sdpBytes, "application", "sdp");
			// TODO 这里的MSRP session 感觉没用起来.需要调整一下.先把 下行的SDP 存起来.
			Sdp sdp1 = (Sdp) UrcsContentFactory.newContent(sdpBytes, Charset.defaultCharset(), MimeType.SDP);
			memberDialog.setLocalMsrpPathBySDP(sdp1.getSdp());
			memberDialog.initMsrpSession(msrp);
		} catch (SdpException e) {
			LOGGER.error("create 200 but createSDP error ", e);
			throw e;
		}
		response = builder.toSipResponse();

		SipURI u = UrcsSipFactory.getAddressFactory().createSipURI(GroupConfiguration.INSTANCE.getGroupPrefix() + groupId, GroupConfiguration.INSTANCE.getGroupDomain());
		Address fromAddress = UrcsSipFactory.getAddressFactory().createAddress(u);
		Contact contact = (Contact) UrcsSipFactory.getHeaderFactory().createContactHeader(fromAddress);
		contact.addIsFocus();
		response.setHeader(contact);
		Header header = UrcsSipFactory.getHeaderFactory().createHeader("P-Service-Param", String.format("conf-uri=\"%s\"", groupUri));
		response.setHeader(header);
		request.getSession().put(GroupSessionKeys.USER_FROM_MOBILE_NO, fromMobileNo);
		request.getSession().put(GroupSessionKeys.GROUP_ID, groupId);
		String tag = TagGenerator.generateTaskTag(fromMobileNo, groupId);
		DedicatedTaskContext ctx = DedicatedTaskContext.putTaskContext(tag);
		ctx.putTask(sessionMessageTask, GroupTaskEvents.GROUP_SESSION_MODE_MESSAGE);
		ctx.putTask(inviteTask, GroupTaskEvents.GROUP_INVITE);
		ctx.putTask(byeTask, GroupTaskEvents.GROUP_BYE);
		ctx.putSession(TASK_CONTEXT_NAME, memberDialog);
		memberDialog.setSipSession(request.getSession());
		memberDialog.setGroupId(groupId);
		memberDialog.setMemberMobile(fromMobileNo);
		request.sendResponse(response);
		ctx.setTaskRunnable();
	}

	/**
	 * 实际处理用户上行的invite 逻辑. 包含激活群会话以及创建群
	 *
	 * @param servletRequest
	 * @throws Exception
	 */
	private void inviteHandler(SipServletRequest servletRequest) throws Exception {
		// 首先判断是那种业务的INVITE
		LOGGER.info("start invite handler");
		String toUri = GroupHeaderUtils.getRequestUriStr(servletRequest);
		// 检查是否为创建群
		boolean isCreate = GroupHeaderUtils.isCreateGroup(toUri);
		MobileNo fromMobileNo = MobileNo.parseFrom(URIUtils.getMobileNo(GroupHeaderUtils.getFromUriByPAssertedIdentity(servletRequest)));
		if (fromMobileNo == null) {
			fromMobileNo = MobileNo.parseFrom(URIUtils.getMobileNo(GroupHeaderUtils.getFromUri(servletRequest)));
		}
		String conversationId = GroupHeaderUtils.getConversationID(servletRequest);
		GroupInfo groupInfo;
		if (isCreate) {
			// 如果是创建群
			// 创建群需要检查是否带有能力. 是否带有邀请列表
			String subject = null;
			Subject subjectHeader = (Subject) servletRequest.getHeader(Subject.NAME);
			if (subjectHeader != null) {
				subject = subjectHeader.getValue();
			}
			if (subject == null) {
				throw new GroupException(GroupError.SUBJECT_REQUIRED);
			}
			if (subject.getBytes("UTF-8").length > 30) {
				throw new GroupException(GroupError.GROUPNAME_EXCEED_LIMIT);
			}
			//如果检查头失败则直接给421 ,需要扩展头
			if (!GroupHeaderUtils.isContainsGPManager(servletRequest)) {
				throw new GroupException(GroupError.SERVICE_NOT_AUTHORISED);
				// 如果到这里成功则继续往下走
			}
			//解析XML.  如果携带列表, 则需要解析列表,并得到列表
			List<MobileNo> inviteList = GroupHeaderUtils.checkAndGetRequireList(servletRequest);
			// 走到这才差不多了, 组装创建群的参数
			CreateGroupArgs createBaseReq = new CreateGroupArgs();
			createBaseReq.setFromMobileNo(fromMobileNo);
			createBaseReq.setConversationId(conversationId);
			createBaseReq.setSubject(subject);
			groupInfo = groupService.checkAndCreate(createBaseReq);
			inviteResponse(servletRequest, fromMobileNo, groupInfo.getGroupId());
			MobileNo[] members = new MobileNo[inviteList.size()];
			inviteList.toArray(members);
			groupService.inviteGroupMembers(groupInfo.getGroupIndex(), fromMobileNo, members);
		} else {
			groupService.check(fromMobileNo, toUri);
			IMPU impu = IMPU.fromUri(servletRequest.getRequestURI());
			String user = URIUtils.getMobileNo(impu.toUri());
			int groupId = Integer.parseInt(user.substring(GroupConfiguration.INSTANCE.getGroupPrefix().length()));
			GroupIndex gi = groupService.getGroupIndex(groupId);
			groupInfo = groupService.getGroupInfo(gi);
			for (GroupMember member : groupInfo.getMembers()) {
				if (member.getMobileNo().equals(fromMobileNo)) {
					if (member.getIdentity() == 61) {
						// 回复同意时，如果当前群成员数量已经达到上限
						// 若群成员数已经达到上限，则向新加入的用户发送BYE消息，将该用户移出群会场，并通知各群成员
						// bye携带reason参数，取值为SIP;cause=200;text=”Group chat is full”
						if (groupInfo.getCurrentMemberCount() >= GroupConfiguration.INSTANCE.getMemberLimit()) {
							GroupByeArgs gba = new GroupByeArgs();
							gba.setText("Group chat is full");
							gba.setCause(200);
							gba.setGroupId(gi.getGroupId());
							gba.setToMember(fromMobileNo);
							outterByeTask.produce(gba);
							return;
						}
						groupService.processGroupInvite(gi, fromMobileNo, true, false);
						groupInfo = groupService.getGroupInfo(gi);
					}
				}
			}
			inviteResponse(servletRequest, fromMobileNo, groupInfo.getGroupId());
		}

	}

	/**
	 * 处理用户发起的 上行invite 逻辑
	 *
	 * @param request
	 * @throws Exception
	 */
	private void inviteMember(SipServletRequest request) throws Exception {
		LOGGER.info("start invite member");
		MobileNo fromMobileNo = (MobileNo) request.getSession().get(GroupSessionKeys.USER_FROM_MOBILE_NO);
		if (fromMobileNo == null) {
			fromMobileNo = MobileNo.parseFrom(URIUtils.getMobileNo(GroupHeaderUtils.getFromUri(request)));
		}
		List<MobileNo> invitees = new ArrayList<>();
		// 先把ReferTo 头的人给加上( 如果带列表是不是没有这个头? )
		ReferTo rtheader = (ReferTo) request.getHeader(ReferTo.NAME);
		if (rtheader != null) {
			invitees.add(MobileNo.parseFrom(URIUtils.getMobileNo(rtheader.getAddress().getURI())));
		}
		// 如果携带列表, 则需要解析列表
		// 居然可以邀请多个.. 这个地方要不要用个set ?.
		List<MobileNo> requireList = GroupHeaderUtils.checkAndGetRequireList(request);
		if (requireList != null) {
			invitees.addAll(requireList);
		}
		MobileNo[] a = new MobileNo[invitees.size()];
		for (int i = 0; i < invitees.size(); i++) {
			a[i] = invitees.get(i);
		}
		SipResponse response = request.createResponse(202);
		response.setReasonPhrase("Accepted");
		int groupId = GroupHeaderUtils.parseGroupId(request);
		GroupIndex index = groupService.getGroupIndex(groupId);
		try {
			groupService.inviteGroupMembers(index, fromMobileNo, a);
		} catch (GroupException e) {
			GroupError groupnameExceedLimit = e.getError();
			Warning warning = new Warning();
			warning.setAgent(groupnameExceedLimit.getAgent());
			warning.setCode(groupnameExceedLimit.getWarningCode());
			warning.setText(groupnameExceedLimit.getText());
			response.setHeader(warning);
			response.setStatusCode(e.getError().getSipCode());
		}
		request.sendResponse(response);
	}

	/**
	 * 处理用户发起的 修改群名称
	 *
	 * @param request
	 * @throws ParseException
	 */
	private void modifySubject(SipServletRequest request) throws ParseException {
		LOGGER.info("GroupServlet.doRefer.subject.");
		SipResponse response = null;
		try {
			//检查gpmanager 头,如果不包含则直接给421 缺少扩展能力的应答.
			if (!GroupHeaderUtils.isContainsGPManager(request)) {
				response = request.createResponse(421, " extension required ");
				request.sendResponse(response);
				return;
			}
			// 获取from 头
			MobileNo fromMobileNo = (MobileNo) request.getSession().get(GroupSessionKeys.USER_FROM_MOBILE_NO);
			if (fromMobileNo == null) {
				fromMobileNo = MobileNo.parseFrom(URIUtils.getMobileNo(GroupHeaderUtils.getFromUri(request)));
			}
			//String groupUri = GroupHeaderUtils.getRequestUriStr(request);
			int groupId = GroupHeaderUtils.parseGroupId(request);
			String conversationID = GroupHeaderUtils.getConversationID(request);
			Subject subjectHeader = (Subject) request.getHeader(Subject.NAME);
			String subject = subjectHeader == null ? null : subjectHeader.getSubject();
			if (subject.getBytes("UTF-8").length > 30) {
				throw new GroupException(GroupError.GROUPNAME_EXCEED_LIMIT);
//				response = request.createResponse(413, "Group name exceed limit");
			} else {
				GroupCommonArgs groupCommonArgs = new GroupCommonArgs();
				groupCommonArgs.setGroupIndex(groupService.getGroupIndex(groupId));
				groupCommonArgs.setFromMobileNo(fromMobileNo);
				groupCommonArgs.setConversationId(conversationID);
				groupService.changeSubject(groupCommonArgs, subject);
				//service 没抛异常就是修改成功了. 否则应该是GroupException
				response = request.createResponse(202);
			}
		} catch (GroupException ex) {
			try {
				GroupError error = ex.getError();
				Warning warning = new Warning();
				warning.setAgent(error.getAgent());
				warning.setCode(error.getWarningCode());
				warning.setText(error.getText());
				response = request.createResponse(error.getSipCode());
				response.setHeader(warning);
			} catch (ParseException e) {
				LOGGER.info("GroupServlet.modifySubject catch ParseException {}", ex);
				throw new RuntimeException(ex);
			} catch (InvalidArgumentException e) {
				LOGGER.info("GroupServlet.modifySubject catch InvalidArgumentException {}", ex);
				throw new RuntimeException(ex);
			}
		} catch (Exception e) {
			LOGGER.warn("modifySubject  error", e);
			response = request.createResponse(500);
		}
		request.sendResponse(response);
	}

	/**
	 * 用户上行 ,修改自己的群成员 昵称
	 *
	 * @param sipServletRequest
	 * @throws Exception
	 */
	private void modifyNickName(SipServletRequest sipServletRequest) throws Exception {
		LOGGER.info("GroupServlet.doRefer.munn", sipServletRequest);
		ReferTo referToHeader = (ReferTo) sipServletRequest.getHeader(ReferTo.NAME);
		int groupId = GroupHeaderUtils.parseGroupId(sipServletRequest);

		String nickName;
		if (referToHeader.getParameter("nn") == null) {
			nickName = StringUtils.EMPTY;
		} else {
			nickName = referToHeader.getParameter("nn");
		}
		GroupIndex index = groupService.getGroupIndex(groupId);
		URI uri = sipServletRequest.getFrom().getAddress().getURI();
		MobileNo fromMobileNo = null;
		if (uri.isSipURI()) {
			SipURI sipURI = (SipURI) uri;
			fromMobileNo = MobileNo.parseFrom(sipURI.getUser());
		} else {
			fromMobileNo = MobileNo.parseFrom(uri.toString());
		}
		groupService.updateGroupMemberNickname(index, fromMobileNo, nickName);
		SipResponse response = sipServletRequest.createResponse(202);
		sipServletRequest.sendResponse(response);
	}

	/**
	 * UE上行 更换群管理
	 *
	 * @param sipServletRequest
	 */
	private void changeAdmin(SipServletRequest sipServletRequest) {
		SipResponse response = sipServletRequest.createResponse(202);
		try {
			LOGGER.info("GroupServlet.doRefer.setplc");
			String plccmd = StringUtils.EMPTY;
			String plcvalue = StringUtils.EMPTY;
			ReferTo referToHeader = (ReferTo) sipServletRequest.getHeader(ReferTo.NAME);
			int groupId = GroupHeaderUtils.parseGroupId(sipServletRequest);
			if (referToHeader.getParameter("plccmd") != null && referToHeader.getParameter("plcvalue") != null) {
				plccmd = referToHeader.getParameter("plccmd");
				plcvalue = referToHeader.getParameter("plcvalue");
			} else {
				response.setStatusCode(400);
				sipServletRequest.sendResponse(response);
				return;
			}
			if (plccmd.equalsIgnoreCase("gaccede")) {
				GroupIndex index = groupService.getGroupIndex(groupId);
				/** 更换群主 */
				URI newManagerUri = null;
				if (plcvalue.equalsIgnoreCase("anyone")) {
					/** 服务端选择一个可以担任群主的人 */
					//TODO 这里需要变更逻辑, 如果找不到则用最早加入群的人当群主. 另外上下文中的uri 考虑改成Mobile
					GroupInfo groupInfo = groupService.getGroupInfo(index);
					for (GroupMember groupMember : groupInfo.getMembers()) {
						if (groupMember.getEtype().equalsIgnoreCase("gpmanage")) {
							newManagerUri = UrcsSipFactory.getAddressFactory().createSipURI(groupMember.getMobileNo().toString(), GroupConfiguration.INSTANCE.getDomain());
							break;
						}
					}
				} else {
					newManagerUri = UrcsSipFactory.getAddressFactory().createURI(plcvalue);
				}
				GroupCommonArgs groupCommonArgs = new GroupCommonArgs();
				groupCommonArgs.setConversationId(sipServletRequest.getHeader(ConversationID.NAME).toString());
				groupCommonArgs.setReferToMobileNo(MobileNo.parseFrom(URIUtils.getMobileNo(newManagerUri)));
				groupCommonArgs.setGroupIndex(index);
				MobileNo fromMobileNo = (MobileNo) sipServletRequest.getSession().get(GroupSessionKeys.USER_FROM_MOBILE_NO);
				if (fromMobileNo == null) {
					fromMobileNo = MobileNo.parseFrom(URIUtils.getMobileNo(GroupHeaderUtils.getFromUri(sipServletRequest)));
				}
				groupCommonArgs.setFromMobileNo(fromMobileNo);
				groupService.changeAdmin(index, groupCommonArgs.getFromMobileNo(), groupCommonArgs.getReferToMobileNo());
				SipResponse sipResponse = sipServletRequest.createResponse(202);
				sipServletRequest.sendResponse(sipResponse);
				return;
			} else {
				response.setStatusCode(GroupError.REFER_TO_FIELD_REQUIRED.getSipCode());
				sipServletRequest.sendResponse(response);
				return;
			}
		} catch (GroupException ex) {
			try {
				GroupError error = ex.getError();
				Warning warning = new Warning();
				warning.setAgent(error.getAgent());
				warning.setCode(error.getWarningCode());
				warning.setText(error.getText());
				response = sipServletRequest.createResponse(error.getSipCode());
				response.setHeader(warning);
			} catch (ParseException e) {
				LOGGER.info("GroupServlet.changeAdmin catch ParseException {}", ex);
				throw new RuntimeException(ex);
			} catch (InvalidArgumentException e) {
				LOGGER.info("GroupServlet.changeAdmin catch InvalidArgumentException {}", ex);
				throw new RuntimeException(ex);
			}
		} catch (Exception e) {
			LOGGER.error("ChangeAdminHandler.changeAdmin error {}", e);
			response = sipServletRequest.createResponse(500);
			sipServletRequest.sendResponse(response);
		}
	}

	/**
	 * UE 上行, 踢出成员
	 *
	 * @param request
	 */
	private void kickGroupMember(SipServletRequest request) {
		SipResponse response = null;
		try {
			LOGGER.info("GroupServlet.doRefer.bye kickGroupMember.", request);
			MobileNo fromMobileNo = (MobileNo) request.getSession().get(GroupSessionKeys.USER_FROM_MOBILE_NO);
			if (fromMobileNo == null) {
				fromMobileNo = MobileNo.parseFrom(URIUtils.getMobileNo(request.getFromHeader().getAddress().getURI()));
			}
			int groupId = GroupHeaderUtils.parseGroupId(request);
			ReferTo referto = (ReferTo) request.getHeader(ReferTo.NAME);
			MobileNo refertoMobileNo = MobileNo.parseFrom(URIUtils.getMobileNo(referto.getAddress().getURI())); // 这是被删除的人的
			//TODO 同样需要做uri 到mobileNo 的转换.
			GroupIndex index = groupService.getGroupIndex(groupId);
			groupService.removeGroupMembers(index, fromMobileNo, refertoMobileNo);
			response = request.createResponse(202);
			request.sendResponse(response);
		} catch (GroupException ex) {
			try {
				GroupError error = ex.getError();
				Warning warning = new Warning();
				warning.setAgent(error.getAgent());
				warning.setCode(error.getWarningCode());
				warning.setText(error.getText());
				response = request.createResponse(error.getSipCode());
			} catch (ParseException e) {
				LOGGER.info("GroupServlet.changeAdmin catch ParseException {}", ex);
				throw new RuntimeException(ex);
			} catch (InvalidArgumentException e) {
				LOGGER.info("GroupServlet.changeAdmin catch InvalidArgumentException {}", ex);
				throw new RuntimeException(ex);
			}
		} catch (Exception ex) {
			LOGGER.error("kickGroupMember  error", ex);
			response = request.createResponse(500, "Server Error");
		}
		request.sendResponse(response);
	}

	/**
	 * 用户UE上行
	 *
	 * @param request
	 * @throws Exception
	 */
	private void byeGroup(SipServletRequest request) throws Exception {

		LOGGER.info("GroupServlet. bye  .", request);
		MobileNo fromMobileNo = MobileNo.parseFrom(URIUtils.getMobileNo(GroupHeaderUtils.getFromUri(request)));
		int groupId = GroupHeaderUtils.parseGroupId(request); // 群的URI 是从requestUri 中获取.
		ReasonList reasonLine = (ReasonList) request.getHeader(Reason.NAME);
		if (reasonLine == null) {
			LOGGER.info(String.format("byeGroupbye GroupID=%s,Mobile=%s", groupId, fromMobileNo));
			groupService.byeGroup(groupId, fromMobileNo, true);
			return;
		}
		Reason reason = reasonLine.get(0);
		// 正常的bye 掉逻辑.  直接返回, 不当退出处理.
		if (!(reason.getCause() == 200) || !REASON_CALL_COMPLETED.equals(reason.getText())) {
			groupService.byeGroup(groupId, fromMobileNo, true);
			return;
		}
		GroupIndex index = groupService.getGroupIndex(groupId);
		GroupInfo groupInfo = groupService.getGroupInfo(index);
		if (index == null || groupInfo == null) {
			request.sendResponse(404, "not found ");
			return;
		}
		String conversationID = GroupHeaderUtils.getConversationID(request);
		GroupCommonArgs groupCommonArgs = new GroupCommonArgs();
		groupCommonArgs.setConversationId(conversationID);
		groupCommonArgs.setGroupIndex(index);
		groupCommonArgs.setFromMobileNo(fromMobileNo);

		//如果是管理, 必须带有字段强制解散群字段
		if (fromMobileNo.equals(groupInfo.getGroupAdmin())) {
			// TODO 需要把gclose 的验证打开
			String contactGroupPolicy = GroupHeaderUtils.tryGetContactGroupPolicy(request);
//			if (!"gclose".equals(contactGroupPolicy)) {
//				throw new GroupException(GroupError.NO_ADMIN_CHOOSABLE);
//			}
			groupService.dismissGroup(index, fromMobileNo);

		} else {
			groupService.quitGroup(index, fromMobileNo);
		}

	}


	/**
	 * 只是尝试激活用户, 不需要消息内容等参数.
	 *
	 * @param args
	 */
	public void activateMember(GroupMessageArgs args) {
		activateAndSendMessage(null, args, false);
	}


	/**
	 * 下行,  T端逻辑, 给用户发文本消息
	 *
	 * @param contentType
	 * @param content
	 */

	public void sendText(GroupMemberDialog dialog, MobileNo referBy, String contentType, long groupStorageId, byte[] content) {
		dialog.sendText(referBy, contentType, groupStorageId, content);
	}

	public void reciveText(int groupId, MobileNo memberMobile, String contentType, byte[] content, ClientType clientType, Date sendTime) throws Exception {
		chatService.sendText(groupId, memberMobile, contentType, content, clientType, null, sendTime);
	}

	public void activateAndSendMessage(DedicatedTaskContext ctx, GroupMessageArgs args, boolean sendMessage) {
		GroupMemberDialog memberDialog = newGroupMemberDialog(args.getToMobile());
		activateAndSendMessage(ctx, args, sendMessage, memberDialog);
	}

	public void activateAndSendMessage(DedicatedTaskContext ctx, GroupMessageArgs args, boolean sendMessage, GroupMemberDialog memberDialog) {
		try {
			if (memberDialog == null) {
				LOGGER.error(String.format("activateAndSendMessage memberDialog is null  args=%s,sendMessage=%s ", args.toJsonObject(), sendMessage));
				return;
			}
			memberDialog.setClean(false);
			SipURI sipURI = enumService.getSipURI(args.getToMobile());
			if (sipURI == null) {
				return;
			}
			final SipServletRequest request = createActiveInviteRequest(args.getGroupId(), args.getToMobile(), memberDialog);
			SipServletFuture future = request.sendRequest();
			memberDialog.setSipSession(request.getSession());
			memberDialog.setGroupId(args.getGroupId());
			memberDialog.setMemberMobile(args.getToMobile());
			if (ctx != null) {
				ctx.putSession(TASK_CONTEXT_NAME, memberDialog);
			} else {
				String tag = TagGenerator.generateTaskTag(args.getToMobile(), args.getGroupId());
				DedicatedTaskContext.putTaskContext(tag).putSession(TASK_CONTEXT_NAME, memberDialog);
			}
			future.setListener(
					new TransactionClientListener() {
						@Override
						public void onTransProvisionalResponse(TransactionClient tc, SipResponse resp) {
							LOGGER.info(String.format(" invite onTransProvisionalResponse \r\n %s", resp));
						}

						@Override
						public void onTransSuccessResponse(TransactionClient tc, SipResponse resp) {
							try {
								memberDialog.bindMsrp(resp);
								memberDialog.hadMoreMessage();
								if (memberDialog.getMediaAlready().get()) {
									LOGGER.info("media ready, can send message.......");
									if (sendMessage) {
										LOGGER.info("after media ready, sendMessage");
										GroupServlet.this.sendText(memberDialog, args.getFromMobile(), args.getContentType(), args.getGroupMessageStorageId(), args.getContent());
									}
								} else {
									if (memberDialog instanceof GroupMemberDialogOutter) {
										LOGGER.info("media not ready , add message to outter Member");
										((GroupMemberDialogOutter) memberDialog).addList(new Combo2(args.getContentType(), args.getContent()));
									} else if (memberDialog instanceof GroupMemberDialogHasOffline) {
										LOGGER.info("media not ready , add message GroupMemberDialogHasOffline");
										GroupServlet.this.sendText(memberDialog, args.getFromMobile(), args.getContentType(), args.getGroupMessageStorageId(), args.getContent());
									} else {
										LOGGER.info("media not ready , add message NULL");

									}
								}
								if (ctx != null) {
									ctx.setTaskRunnable();
								}
							} catch (Exception ex) {
								LOGGER.error("activateAndSendMessage error : {} ", ex);
							}
						}

						@Override
						public void onTransFailureResponse(TransactionClient tc, SipResponse resp) {
							// TODO

							try {
								// 本来成功才需要初始化, 现在失败也得初始化了. 目的是为了用这个来处理用户离线消息.
								// 随后考虑把这个逻辑放到Service里面. 就不需要在这处理了.
								boolean isLocal = userLocatorService.lookup(memberDialog.getMemberMobile()).getType().equals(UserType.INNER);
								if (isLocal) {
									// 本域的有离线,可以复用
									if (sendMessage)
										GroupServlet.this.sendText(memberDialog, args.getFromMobile(), args.getContentType(), args.getGroupMessageStorageId(), args.getContent());
								} else {
									// 外域的取消. 重新创建.
									LOGGER.error(String.format("mobile=%s groupId=%s invite fail ", memberDialog.getMemberMobile(), memberDialog.getGroupId()));
//									String tag = TagGenerator.generateTaskTag(args.getToMobile(), args.getGroupId());
//									DedicatedTaskContext.putTaskContext(tag).removeSession(TASK_CONTEXT_NAME);

									ctx.setTaskRunnable();
								}
							} catch (Exception ex) {
								LOGGER.error(" onTransFailureResponse ", ex);
							}

						}

						@Override
						public void onTransTimeout(TransactionClient tc) {
							// TODO

							try {
								// 本来成功才需要初始化, 现在失败也得初始化了. 目的是为了用这个来处理用户离线消息.
								// 随后考虑把这个逻辑放到Service里面. 就不需要在这处理了.
								boolean isLocal = userLocatorService.lookup(memberDialog.getMemberMobile()).getType().equals(UserType.INNER);
								if (isLocal) {
									// 本域的有离线,可以复用
									if (sendMessage)
										GroupServlet.this.sendText(memberDialog, args.getFromMobile(), args.getContentType(), args.getGroupMessageStorageId(), args.getContent());
								} else {
									// 外域的取消. 重新创建.
									LOGGER.error(String.format("mobile=%s groupId=%s invite fail ", memberDialog.getMemberMobile(), memberDialog.getGroupId()));
//									String tag = TagGenerator.generateTaskTag(args.getToMobile(), args.getGroupId());
//									DedicatedTaskContext.putTaskContext(tag).removeSession(TASK_CONTEXT_NAME);

									ctx.setTaskRunnable();
								}
							} catch (Exception ex) {
								LOGGER.error(" onTransTimeout ", ex);
							}
						}
					}
			);

		} catch (Exception ex) {
			LOGGER.error(String.format("Invite fail args=%s", args.toJsonObject().toString()), ex);
		}
	}

	private SipServletRequest createActiveInviteRequest(int groupId, MobileNo fromMobile, GroupMemberDialog memberDialog) throws Exception {
		boolean isLocal = userLocatorService.lookup(fromMobile).getType().equals(UserType.INNER);
		MsrpSession session = msrpFactory.createSession();
		String wrappedType = "text/plain application/im-iscomposing+xml application/vnd.gsma.rcspushlocation+xml";
		String acceptType = "text/plain message/cpim application/im-iscomposing+xml multipart/mixed";
		byte[] sdpBytes = MsrpUtils.createMsrpSdp(session.getSessionId(), session.getLocalSocketAddress(), false, acceptType, false ? MsrpUtils.MsrpTransferMode.SENDONLY : MsrpUtils.MsrpTransferMode.SENDRECV, wrappedType);
		memberDialog.initMsrpSession(session);
		Sdp sdp1 = (Sdp) UrcsContentFactory.newContent(sdpBytes, Charset.defaultCharset(), MimeType.SDP);
		memberDialog.setLocalMsrpPathBySDP(sdp1.getSdp());
		UrcsMultipartMimeContent content = UrcsMultipartMimeContent.newInstance();
		content.addContent(UrcsContentFactory.newContent(sdpBytes, Charset.defaultCharset(), MimeType.SDP));
		GroupIndex groupIndex = groupService.getGroupIndex(groupId);
		GroupInfo info = groupService.getGroupInfo(groupIndex);
		SipURI groupSipUri = UrcsSipFactory.getAddressFactory().createSipURI(GroupConfiguration.INSTANCE.getGroupPrefix() + groupId, GroupConfiguration.INSTANCE.getGroupDomain());
		SipRequest sipRequest = UrcsSipFactory.getMessageFactory().createRequest(connector.getSipProvider(), Request.INVITE, fromMobile.toAddress(), UrcsSipFactory.getAddressFactory().createAddress(groupSipUri));
		SipMessageBuilder sipMessageBuilder = new SipMessageBuilder(SipServletRequestFactory.createRequest(sipRequest, connector, this))
				.callId(UUID.randomUUID().toString())
				.cSeq(1, Request.INVITE)
				.route(isLocal ? cscfAddress : isbcAddress)
				.recordRoute(UrcsSipFactory.getAddressFactory().createAddress(asAddress))
				.pai(groupSipUri)
				.pas("ims.icsi.oma.cpm.session")
				.acceptContact(GroupConstants.PREFIX_ACCEPT_CONTACT + GroupConstants.G_3GPP_ICSI_REF_SESSION_KEY_VALUE)
				.sessionExires(3600, "uas")
				.minSE(600)
				.supported("timer")
				.subject(info.getSubject())
				.allow("INVITE,ACK,CANCEL,BYE,UPDATE,REFER,SUBSCRIBE")
				.contact(GroupConfiguration.INSTANCE.getGroupPrefix() + groupId, GroupConfiguration.INSTANCE.getGroupDomain(), GroupConstants.G_3GPP_ICSI_REF_SESSION_KEY_VALUE, true)
				.conversationId(info.getConversationId())
				.contributionId(UUID.randomUUID().toString())
				.allowEvents("conference")
				.accept("application", "sdp")
				.userAgent("Conf-serv/3GPP")
				.requestUri(UrcsSipFactory.getAddressFactory().createTelURI(fromMobile.toString()));
		sipMessageBuilder.referBy(UrcsSipFactory.getAddressFactory().createAddress(groupSipUri));
		SipServletRequest request = sipMessageBuilder.toSipServletReqeust();
		request.setHeader(new PCalledPartyID((AddressImpl) fromMobile.toAddress()));
		request.setContent(content.toByteArray(), content.getContentTypeHeader());
		return request;
	}

	private GroupMemberDialog newGroupMemberDialog(MobileNo toMemberMobile) {
		GroupMemberDialog memberDialog = null;
		// 账号域
		boolean isLocal = userLocatorService.lookup(toMemberMobile).getType().equals(UserType.INNER);
		LOGGER.info(String.format("newGroupMemberDialog MobileNo =%s isLocal=%s", toMemberMobile, isLocal));
		if (isLocal) {
			memberDialog = new GroupMemberDialogHasOffline(this, offlineMessageService, userLocatorService, userSessionService, groupService, groupMessageLogService);
		} else {
			memberDialog = new GroupMemberDialogOutter(this, groupMessageLogService);
		}
		return memberDialog;
	}

	/**
	 * 下行, T端逻辑, 邀请加入群
	 */
	public void inviteMemberDialog(GroupInviteArgs args) {
		try {

			GroupMemberDialog memberDialog = newGroupMemberDialog(args.getReferredTo());
			final SipServletRequest request = createInviteRequest(args.getGroupId(), args.getSubject(), args.getDomain(),
					args.getConversationId(), args.getReferredTo(), args.getReferredBy(), memberDialog, args.isOffline());
			SipServletFuture future = request.sendRequest();
			memberDialog.setGroupId(args.getGroupId());
			memberDialog.setMemberMobile(args.getReferredTo());
			DedicatedTaskContext[] ctx = new DedicatedTaskContext[1];
			future.setListener(new TransactionClientListener() {
				@Override
				public void onTransProvisionalResponse(TransactionClient tc, SipResponse resp) {
					LOGGER.info("onTransProvisionalResponse");
					try {
						// sendOnly
						if (args.isOffline()) {
							LOGGER.info("onTransProvisionalResponse, this is a offline invite");
							return;
						}
						if (resp.getStatusCode() == 180) {
							String tag = TagGenerator.generateTaskTag(args.getReferredTo(), args.getGroupId());
							ctx[0] = DedicatedTaskContext.putTaskContext(tag);
							ctx[0].putTask(inviteTask, GroupTaskEvents.GROUP_INVITE);
							ctx[0].putSession(TASK_CONTEXT_NAME, memberDialog);
							memberDialog.setSipSession(request.getSession());
						}
					} catch (Exception ex) {
						LOGGER.error("processGroupInvite failed {}", ex);
					}
				}

				@Override
				public void onTransSuccessResponse(TransactionClient tc, SipResponse resp) {
					try {
						// sendOnly
						if (args.isOffline()) {
							LOGGER.info("onTransSuccessResponse, this is a offline invite");
							return;
						}
						if (resp.getStatusCode() == 200) {
							memberDialog.bindMsrp(resp);
							GroupIndex gi = groupService.getGroupIndex(args.getGroupId());
							GroupInfo groupInfo = groupService.getGroupInfo(gi);
							boolean isReject = true;
							for (GroupMember member : groupInfo.getMembers()) {
								if (member.getMobileNo().equals(args.getReferredTo())) {
									isReject = false;
								}
							}
							if (isReject) {
								GroupByeArgs gba = new GroupByeArgs();
								gba.setText("Booted");
								gba.setCause(200);
								gba.setGroupId(gi.getGroupId());
								gba.setToMember(args.getReferredTo());
								outterByeTask.produce(gba);
							} else {
								// 回复同意时，如果当前群成员数量已经达到上限
								// 若群成员数已经达到上限，则向新加入的用户发送BYE消息，将该用户移出群会场，并通知各群成员
								// bye携带reason参数，取值为SIP;cause=200;text=”Group chat is full”
								if (groupInfo.getCurrentMemberCount() >= GroupConfiguration.INSTANCE.getMemberLimit()) {
									GroupByeArgs gba = new GroupByeArgs();
									gba.setText("Group chat is full");
									gba.setCause(200);
									gba.setGroupId(gi.getGroupId());
									gba.setToMember(args.getReferredTo());
									outterByeTask.produce(gba);
									return;
								}
								groupService.processGroupInvite(gi, args.getReferredTo(), true, false);
								request.getSession().put(GroupSessionKeys.USER_FROM_MOBILE_NO, args.getReferredTo());
								memberDialog.setSipSession(request.getSession());
								String tag = TagGenerator.generateTaskTag(args.getReferredTo(), args.getGroupId());
								if (ctx[0] != null) {
									ctx[0].putTask(sessionMessageTask, GroupTaskEvents.GROUP_SESSION_MODE_MESSAGE);
									ctx[0].putTask(inviteTask, GroupTaskEvents.GROUP_INVITE);
									ctx[0].putTask(byeTask, GroupTaskEvents.GROUP_BYE);
									ctx[0].putSession(TASK_CONTEXT_NAME, memberDialog);
									ctx[0].setTaskRunnable();
								} else {
									ctx[0] = DedicatedTaskContext.putTaskContext(tag);
									ctx[0].putTask(sessionMessageTask, GroupTaskEvents.GROUP_SESSION_MODE_MESSAGE);
									ctx[0].putTask(inviteTask, GroupTaskEvents.GROUP_INVITE);
									ctx[0].putTask(byeTask, GroupTaskEvents.GROUP_BYE);
									ctx[0].putSession(TASK_CONTEXT_NAME, memberDialog);
									ctx[0].setTaskRunnable();
								}
							}

						} else {
							LOGGER.info(String.format("code =%s", resp.getStatusCode()));
						}
					} catch (Exception ex) {
						LOGGER.error("processGroupInvite failed {}", ex);
					}
				}

				@Override
				public void onTransFailureResponse(TransactionClient tc, SipResponse resp) {
					LOGGER.info(String.format("onTransFailureResponse invite offline getReferredTo=%s ,getGroupId=%s,getReferredBy=%s", args.getReferredTo(), args.getGroupId(), args.getReferredBy()));

					if (resp.getStatusCode() == 603) {
						// 拒绝什么都不处理
//						try {
//							GroupIndex gi = groupService.getGroupIndex(args.getGroupId());
//							groupService.processGroupInvite(gi, args.getReferredTo(), false, false);
//						} catch (Exception e) {
//							LOGGER.error("reject group invite error :{}", e);
//						}
					} else {
						if (UserType.INNER.equals(userLocatorService.lookup(args.getReferredTo()).getType())) {
							offlineMessageService.saveInvite(args.getReferredTo(), args.getGroupId(), args.getReferredBy());
						} else {
							LOGGER.debug(String.format("invite fail UserType.INNER ReferredTo=%s, GroupId=%s, ReferredBy=%s", args.getReferredTo(), args.getGroupId(), args.getReferredBy()));
						}

						if (resp.getStatusCode() == 487) {
							GroupMessageArgs gma = new GroupMessageArgs();
							gma.setGroupId(args.getGroupId());
							gma.setSubject(args.getSubject());
							gma.setDomain(args.getDomain());
							gma.setConversationId(args.getConversationId());
							gma.setToMobile(args.getReferredTo());
							activateMember(gma);
						}
					}
				}

				@Override
				public void onTransTimeout(TransactionClient tc) {
					LOGGER.info(String.format("onTransTimeout invite offline getReferredTo=%s ,getGroupId=%s,getReferredBy=%s", args.getReferredTo(), args.getGroupId(), args.getReferredBy()));
					offlineMessageService.saveInvite(args.getReferredTo(), args.getGroupId(), args.getReferredBy());

				}
			});
		} catch (Exception ex) {
			LOGGER.error("inviteMemberDialog error : {}", ex);
		}

	}

	/**
	 * 依据一堆参数创建request
	 *
	 * @param groupId
	 * @param subject
	 * @param groupDomain
	 * @param conversationId
	 * @param toMobile
	 * @param referBy        如果referBy 为空,则用群的URI 拼一个. 我擦. 本来想改一下这个在构造参数的时候传递过来.. 太多了实在改不动...
	 * @param memberDialog
	 * @return
	 * @throws ParseException
	 * @throws InvalidArgumentException
	 */
	private SipServletRequest createInviteRequest(int groupId, String subject, String groupDomain, String conversationId, MobileNo toMobile, MobileNo referBy, GroupMemberDialog memberDialog, boolean isOffline) throws Exception {

		boolean isLocal = userLocatorService.lookup(toMobile).getType().equals(UserType.INNER);
		MsrpSession session = msrpFactory.createSession();
		String wrappedType = "text/plain application/im-iscomposing+xml application/vnd.gsma.rcspushlocation+xml";
		String acceptType = "text/plain message/cpim application/im-iscomposing+xml multipart/mixed";
		byte[] sdpBytes = MsrpUtils.createMsrpSdp(session.getSessionId(), session.getLocalSocketAddress(), false, acceptType, isOffline ? MsrpUtils.MsrpTransferMode.SENDONLY : MsrpUtils.MsrpTransferMode.SENDRECV, wrappedType);
		memberDialog.initMsrpSession(session);
		Sdp sdp1 = (Sdp) UrcsContentFactory.newContent(sdpBytes, Charset.defaultCharset(), MimeType.SDP);
		memberDialog.setLocalMsrpPathBySDP(sdp1.getSdp());
		UrcsMultipartMimeContent content = UrcsMultipartMimeContent.newInstance();
		content.addContent(UrcsContentFactory.newContent(sdpBytes, Charset.defaultCharset(), MimeType.SDP));
		GroupInfo info = groupService.getGroupInfo(groupService.getGroupIndex(groupId));
		StringBuffer sb = new StringBuffer();
		sb.append("<?xml version=\"1.0\" encoding=\"UTF-8\"?>");
		sb.append("<resource-lists xmlns=\"urn:ietf:params:xml:ns:resource-lists\" xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\" xmlns:cp=\"urn:ietf:params:xml:ns:copyControl\">");
		sb.append("<list>");
		info.getMembers().forEach(groupMember -> sb.append("<entry uri = \"tel:" + groupMember.getMobileNo().toString() + "\" cp:copyControl=\"to\" />"));
		sb.append("</list>");
		sb.append("</resource-lists>");
		ResourceListArgs arg = new ResourceListArgs();
		arg.parseXmlFrom(sb.toString());
		SIPHeader header1 = (SIPHeader) UrcsSipFactory.getHeaderFactory().createHeader("Content-Disposition", "recipient-list");
		byte[] buffer = arg.toXmlByteArray();
		SIPHeader header2 = (SIPHeader) UrcsSipFactory.getHeaderFactory().createHeader("Content-Length", String.valueOf(buffer.length));
		if (buffer.length > 13 * 1024) {
			SIPHeader header3 = (SIPHeader) UrcsSipFactory.getHeaderFactory().createHeader(SIPHeaderNames.CONTENT_ENCODING, "gzip");
			content.addContent(UrcsContentFactory.newContent(buffer, Charset.defaultCharset(), MimeType.RecipientList, header1, header2, header3));
		} else {
			content.addContent(UrcsContentFactory.newContent(buffer, Charset.defaultCharset(), MimeType.RecipientList, header1, header2));
		}
		SipURI groupSipUri = UrcsSipFactory.getAddressFactory().createSipURI(GroupConfiguration.INSTANCE.getGroupPrefix() + groupId, groupDomain);
		SipRequest sipRequest = UrcsSipFactory.getMessageFactory().createRequest(connector.getSipProvider(), Request.INVITE, toMobile.toAddress(), UrcsSipFactory.getAddressFactory().createAddress(groupSipUri));
		SipMessageBuilder sipMessageBuilder = new SipMessageBuilder(SipServletRequestFactory.createRequest(sipRequest, connector, this))
				.callId(UUID.randomUUID().toString())
				.cSeq(1, Request.INVITE)
				.route(isLocal ? cscfAddress : isbcAddress)
				.recordRoute(UrcsSipFactory.getAddressFactory().createAddress(asAddress))
				.pai(groupSipUri)
				.pas("ims.icsi.oma.cpm.session")
				.acceptContact(GroupConstants.PREFIX_ACCEPT_CONTACT + GroupConstants.G_3GPP_ICSI_REF_SESSION_KEY_VALUE)
				.sessionExires(3600, "uas")
				.minSE(600)
				.supported("timer")
				.subject(subject != null ? subject : "")
				.allow("INVITE,ACK,CANCEL,BYE,UPDATE,REFER,SUBSCRIBE")
				.contact(GroupConfiguration.INSTANCE.getGroupPrefix() + groupId, groupDomain, GroupConstants.G_3GPP_ICSI_REF_SESSION_KEY_VALUE, true)
				.conversationId(conversationId)
				.contributionId(UUID.randomUUID().toString())
				.allowEvents("conference")
				.accept("application", "sdp")
				.userAgent("Conf-serv/3GPP")
				.requestUri(UrcsSipFactory.getAddressFactory().createTelURI(toMobile.toString()));
		if (referBy == null) {
			sipMessageBuilder.referBy(UrcsSipFactory.getAddressFactory().createAddress(groupSipUri));
		} else {
			sipMessageBuilder.referBy(referBy.toAddress());
		}

		SipServletRequest request = sipMessageBuilder.toSipServletReqeust();
		request.setHeader(new PCalledPartyID((AddressImpl) toMobile.toAddress()));
		request.setContent(content.toByteArray(), content.getContentTypeHeader());
		return request;
	}

	public void byeDialog(DedicatedTaskContext ctx, GroupMemberDialog groupMemberDialog, GroupByeArgs groupByeArgs) throws ParseException, InvalidArgumentException {
		if (null != groupByeArgs) {
			Reason reason = new Reason();
			reason.setProtocol("SIP");
			reason.setCause(groupByeArgs.getCause());
			reason.setText(groupByeArgs.getText());
			byeDialog(ctx, groupMemberDialog, reason);
		} else {
			LOGGER.warn(String.format("byeDialog groupByeArgs is null"));
		}


	}

	/**
	 * 踢出群
	 *
	 * @param groupMemberDialog
	 */
	private void byeDialog(DedicatedTaskContext ctx, GroupMemberDialog groupMemberDialog, Reason reason) throws ParseException, InvalidArgumentException {

		SipURI groupUri = UrcsSipFactory.getAddressFactory().createSipURI(GroupConfiguration.INSTANCE.getGroupPrefix() + groupMemberDialog.getGroupId(), GroupConfiguration.INSTANCE.getGroupDomain());
		Address fromAddress = UrcsSipFactory.getAddressFactory().createAddress(groupUri);
		SipRequest request = UrcsSipFactory.getMessageFactory().createRequest(groupMemberDialog.getSipSession().getConnector().getSipProvider(), Request.BYE, groupMemberDialog.getMemberMobile().toAddress(), fromAddress);
		request.setHeader(reason);
		SipServletRequest byeReq = SipServletRequestFactory.createRequest(request, groupMemberDialog.getSipSession());
		SipServletFuture future = byeReq.sendRequest();
		future.setListener(new TransactionClientListener() {
			@Override
			public void onTransProvisionalResponse(TransactionClient tc, SipResponse resp) {

			}

			@Override
			public void onTransSuccessResponse(TransactionClient tc, SipResponse resp) {
				groupMemberDialog.closeMsrp();
				groupMemberDialog.setSipSession(null);
				groupMemberDialog.setClean(true);
			}

			@Override
			public void onTransFailureResponse(TransactionClient tc, SipResponse resp) {

			}

			@Override
			public void onTransTimeout(TransactionClient tc) {

			}
		});
		String tag = TagGenerator.generateTaskTag(groupMemberDialog.getMemberMobile(), groupMemberDialog.getGroupId());
		DedicatedTaskContext.removeTask(tag);
	}

	/**
	 * bye掉连接
	 */
	public void close(DedicatedTaskContext ctx) {
		GroupMemberDialog dialog = (GroupMemberDialog) ctx.getSession(GroupServlet.TASK_CONTEXT_NAME);
		if (dialog != null) {
			dialog.closeDialog();
			ctx.removeSession(GroupServlet.TASK_CONTEXT_NAME);
		}
	}

}